//
//  parser.hpp
//  utilities
//
//  Created by Sam Jaffe on 2/4/15.
//  Copyright (c) 2015 Sam Jaffe. All rights reserved.
//

#pragma once

#include "config.hpp"
#include "parameter.hpp"
#include "exception.hpp"

namespace options {
    
    namespace {
        template <typename arg_t>
        constexpr const char* infer_type() {
            return "arg";
        }
        
        template <>
        constexpr const char* infer_type<std::string>() {
            return "string";
        }
        
        template <>
        constexpr const char* infer_type<bool>() {
            return "bool";
        }
        
        template <>
        constexpr const char* infer_type<char>() {
            return "char";
        }
        
        template <>
        constexpr const char* infer_type<int>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<short>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<long>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<long long>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<unsigned char>() {
            return "char";
        }
        
        template <>
        constexpr const char* infer_type<unsigned int>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<unsigned short>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<unsigned long>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<unsigned long long>() {
            return "int";
        }
        
        template <>
        constexpr const char* infer_type<float>() {
            return "float";
        }
        
        template <>
        constexpr const char* infer_type<double>() {
            return "float";
        }
        
        template <typename arg_t, typename... args_t>
        struct infer_each_rec {
            std::vector<std::string>& operator()(std::vector<std::string> &v) {
                v.push_back(infer_type<arg_t>());
                return infer_each_rec<args_t...>()(v);
            }
        };
        
        template <typename arg_t>
        struct infer_each_rec<arg_t> {
            std::vector<std::string>& operator()(std::vector<std::string> &v) {
                v.push_back(infer_type<arg_t>());
                return v;
            }
        };
        
        template <typename args_t>
        struct infer_each {
            operator std::vector<std::string>() {
                std::vector<std::string> v{infer_type<args_t>()};
                return v;
            }
        };
        
        template <typename... args_t>
        struct infer_each<std::tuple<args_t...>> {
            operator std::vector<std::string>() {
                std::vector<std::string> v;
                infer_each_rec<args_t...>()(v);
                std::reverse(v.begin(), v.end());
                return v;
            }
        };
        
        template <typename arg0_t, typename arg1_t>
        struct infer_each<std::pair<arg0_t, arg1_t>> {
            operator std::vector<std::string>() {
                std::vector<std::string> v;
                infer_each_rec<arg0_t, arg1_t>()(v);
                std::reverse(v.begin(), v.end());
                return v;
            }
        };
        
        template <typename arg_t, std::size_t num>
        struct infer_each<std::array<arg_t, num>> {
            operator std::vector<std::string>() {
                std::vector<std::string> v(infer_type<arg_t>(), num);
                return v;
            }
        };
    }
    
    template <class type>
    inline std::function<void(std::string*)> delimited(type& ref, const std::string& delim) {
        return [&ref, &delim](std::string* args) {
            boost::split(ref, *args, boost::is_any_of(delim/*","*/));
        };
    }

    template <typename opt, typename type>
    vparameter::parser make_vargs_parser(type& container, std::function<bool(const std::string&)> end_condition) {
        return [&container, end_condition](std::string* args) {
            int i = 0;
            while (args != parser<opt>::end() && !end_condition(*args)) {
                ++i;
                container.push_back(*args++);
            }
            return i;
        };
    }
    
    template <typename opt, typename type>
    vparameter::parser make_vargs_parser(type& container, int min, int max, std::function<bool(const std::string&)> end_condition) {
        return [&container, min, max, end_condition](std::string* args) {
            int i = 0;
            while (args != parser<opt>::end() && !end_condition(*args) && i++ <= max) {
                container.push_back(*args++);
            }
            if (i < min) {
                throw not_enough_arguments{min, i};
            }
            return i;
        };
    }
    
    template <typename opt, typename type>
    inline parameter::parser make_commalist_parser(type& container) {
        return delimited(container, ",");
    }

    template <class opt> template <class type>
    inline void parser<opt>::addopt(const std::string& name, type& val,
                                    const std::string& desc) {
        addopt({name}, val, false, desc);
    }
    
    template <class opt> template <class type>
    inline void parser<opt>::addopt(const std::string& name, type& val, bool required,
                                    const std::string& desc) {
        addopt({name}, val, required, desc);
    }
    
    template <class opt> template <class type>
    inline void parser<opt>::addopt(str_list names, type& val,
                                    const std::string& desc) {
        add_each_option(names, new options::parameter(names, val), false, infer_each<type>(), desc);
    }
    
    template <class opt> template <class type>
    inline void parser<opt>::addopt(str_list names, type& val, bool required,
                                    const std::string& desc) {
        add_each_option(names, new options::parameter(names, val), required, infer_each<type>(), desc);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, int arity,
                                    parameter::parser parser,
                                    const std::string& desc, const str_vec & argument_types) {
        addopt({name}, arity, parser, false, desc, argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, int arity,
                                    parameter::parser parser, bool required,
                                    const std::string& desc, const str_vec & argument_types) {
        addopt({name}, arity, parser, required, desc, argument_types);
    }
  
    template <class opt>
    inline void parser<opt>::addopt(str_list names, int arity,
                                    parameter::parser parser,
                                    const std::string& desc, const str_vec & argument_types) {
        add_each_option(names, new options::parameter(names, arity, parser), false, argument_types.empty() ? str_vec("arg", arity) : argument_types, desc);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(str_list names, int arity,
                                    parameter::parser parser, bool required,
                                    const std::string& desc, const str_vec & argument_types) {
        add_each_option(names, new options::parameter(names, arity, parser), required, argument_types.empty() ? str_vec("arg", arity) : argument_types, desc);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, vparameter::parser parser,
                                    const std::string& desc, const str_vec & argument_types) {
        addopt({name}, parser, false, desc, argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, vparameter::parser parser, bool required,
                                    const std::string& desc, const str_vec & argument_types) {
        addopt({name}, parser, required, desc, argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(str_list names, vparameter::parser parser,
                                    const std::string& desc, const str_vec & argument_types) {
        add_each_option(names, new options::vparameter(names, parser), false, argument_types, desc);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(str_list names, vparameter::parser parser, bool required,
                                    const std::string& desc, const str_vec & argument_types) {
        add_each_option(names, new options::vparameter(names, parser), required, argument_types, desc);
    }
    
    template <class opt>
    template <class type>
    inline void parser<opt>::addopt(const std::string& name, type& val, const char* desc) {
        addopt(name, val, std::string(desc));
    }
    
    template <class opt>
    template <class type>
    inline void parser<opt>::addopt(str_list names, type& val, const char* desc) {
        addopt(names, val, std::string(desc));
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, int arity, parameter::parser parser,
                                    const char* desc, const str_vec & argument_types) {
        addopt(name, parser, std::string(desc), argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(str_list names, int arity, parameter::parser parser,
                                    const char* desc, const str_vec & argument_types) {
        addopt(names, parser, std::string(desc), argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(std::string name, vparameter::parser parser,
                                    const char* desc, const str_vec & argument_types) {
        addopt(name, parser, std::string(desc), argument_types);
    }
    
    template <class opt>
    inline void parser<opt>::addopt(str_list names, vparameter::parser parser,
                                    const char* desc, const str_vec & argument_types) {
        addopt(names, parser, std::string(desc), argument_types);
    }

    
    template <class opt>
    bool parser<opt>::parse_one_option(std::string* &arglist) {
        int idx;
        if (*arglist == "--help") {
            value().usage(config().mainprog);
            config().print_options();
            exit(EXIT_SUCCESS);
        }
        typename config::options_map::iterator it;
        if (is_dash_arg(*arglist)) {
            if ((it = config().find_option(*arglist)) != config().no_option()) {
                if (arglist + it->second->arity() + 1 >= end()) {
                    throw index_out_of_bounds{};
                } else {
                    config().required_arguments.erase(it->second);
                    it->second->parse(++arglist);
                    arglist += it->second->arity();
                }
            } else if (config().die_on_illegal) {
                throw illegal_option_signature{arglist->c_str() + 1};
            }
        }
        // Should this be included? or some other type?
        else if ((idx = arglist->find('=')) != std::string::npos
                 && (it = config().find_option(arglist->substr(0, idx+1))) != config().no_option()) {
            std::string arg = arglist->substr(idx+1);
            config().required_arguments.erase(it->second);
            it->second->parse(&arg);
            ++arglist;
        } else {
            return false;
        }
        return true;
    }
    
    template <class opt>
    void parser<opt>::parse_options(int argc, const char* argv[]) {
        init_usage_options(argv[0]);
        value(); // Init Options
        config().main_args.reserve(argc-1);
        
        std::string args[argc];
        for (int i = 0; i < argc; ++i) {
            args[i] = argv[i];
        }
        config().end_of_args = args + argc;
        std::string* arglist = args + 1;
        while (arglist - args < argc) {
            // Primary argument detected
            if(!parse_one_option(arglist)) {
                config().main_args.push_back(*arglist++);
            }
        }
        if (!config().required_arguments.empty()) {
            throw missing_options{config().required_arguments};
        }
    }
    
    template <class opt>
    int parser<opt>::parse_options_until_verb(int argc, const char* argv[]) {
        init_usage_options(argv[0]);
        value(); // Init Options
        std::string args[argc];
        for (int i = 0; i < argc; ++i) {
            args[i] = argv[i];
        }
        config().end_of_args = args + argc;
        std::string* arglist = args + 1;
        while (arglist - args < argc) {
            // Primary verb detected
            if(!parse_one_option(arglist)) {
                return arglist - args;
            }
        }
        return -1;
    }
    
    template <class opt>
    inline void parser<opt>::add_each_option(std::initializer_list<std::string> names, options::parameter_base* par, bool required, const str_vec & argument_types, const std::string& desc) {
        config().parsers.push_back(par);
        if (required) { config().required_arguments.insert(par); }
        std::string first = names.begin()->front() == '-' || names.begin()->back() == '=' ? *names.begin() : "-" + *names.begin();
        for (auto it = names.begin(); it != names.end(); ++it) {
            int start = 0;
            std::string arg = it->front() == '-' || it->back() == '=' ? *it : "-" + *it;
            while ((*it)[start] == '-') { start++; }
            config().usage_info.emplace(it->substr(start), description(arg, argument_types, it == names.begin() ? desc : "alias for '" + first + "'"));
            if (it->back() == '=' && par->arity() != 1) {
                throw illegal_assign_arity{};
            }
            config().parameter_mapping.emplace(arg, par);
        }
    }
}
